{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<img src=\"../common/rfsoc_book_banner.jpg\" alt=\"University of Strathclyde\" align=\"left\">"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Notebook Set E\n",
    "\n",
    "---\n",
    "\n",
    "## 01 - Amplitude Modulation\n",
    "This notebook will cover the modulation of a baseband signal to an Intermediate Frequency (IF) or Radio Frequency (RF) band for transmission and show how this original signal can be perfectly reconstructed at the receiver using demodulation. We will first explore a simple Amplitude Modulation and Demodulation example. These approaches will be explored step by step. The underlying mathematics will be examined and the signals plotted for visualisation in both time and frequency domains to understand each stage of the process.\n",
    "\n",
    "## Table of Contents\n",
    "* [1. Introduction](#introduction)\n",
    "* [2. Amplitude Modulation and Demodulation](#amplitude_modulation)\n",
    "* [3. Amplitude Modulation of a Baseband Signal](#mod)\n",
    "* [4. Demodulation of the Baseband Signal](#demod)\n",
    "* [5. Demodulation Phase Error](#phase_error)\n",
    "* [6. Conclusion](#conclusion)\n",
    "\n",
    "## Revision\n",
    "* **v1.0** | 22/12/22 | _First Revision_\n",
    "\n",
    "---\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 1. Introduction <a class=\"anchor\" id=\"introduction\"></a>\n",
    "\n",
    "Modulating (or mixing) is the process of combining two signals through multiplication. Modulation is the shifting of an information signal into an allocated frequency band. Demodulation is the reverse process, where information is translated back to baseband. This process is highlighted in Figure 1."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<figure>\n",
    "<img src='./images/modulation.svg' height='50%' width='50%'/>\n",
    "    <figcaption><b>Figure 1: Overview of modulation and demodulation.</b></figcaption>\n",
    "</figure>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "At the transmitter, baseband information can be modulated or mixed to a carrier frequency, which may typically be in the range 10’s of MHz to multiple GHz. At the receiver, this modulated signal can be demodulated or mixed from the carrier frequency back to baseband. \n",
    "\n",
    "This series of notebooks will first give an introduction to the required steps using simple Amplitude Modulation. Then, we will introduce the popular Quadrature Amplitude Modulation (QAM) scheme, used by many modern radio communications systems, in later notebooks."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 2. Amplitude Modulation and Demodulation <a class=\"anchor\" id=\"amplitude_modulation\"></a>\n",
    "\n",
    "Amplitude Modulation is the process of modifying one signal by another through the altering of signal amplitude. This makes for a good introduction to the world of modulation as the process and circuit design are straightforward. The weakness of AM however is that upon reconstruction the signal will have halved in amplitude.\n",
    "\n",
    "The remainder of this notebook consists of descriptions, diagrams, code cells and plots walking you through the process of modulating a baseband signal to RF, transmitting the signal, receiving and demodulating. We will showcaase how a baseband signal can be reconstructed after being transmitted at RF. "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "For this notebook we will be using NumPy for computation and MatplotLib for visualisation of our waveforms. Let us begin by importing these libraries below."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "import matplotlib.pyplot as plt"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 3. Amplitude Modulation of a Baseband Signal <a class=\"anchor\" id=\"mod\"></a>\n",
    "\n",
    "We will begin with a simple modulation scheme, mixing a low frequency baseband signal, $g(t)$, with a high frequency carrier, $c(t)$, to produce a modulated signal, $s(t)$. This signal can then be transmitted from an RF antenna. The design of the RF transmitter can be seen in Figure 2."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<figure>\n",
    "<img src='./images/amplitude_modulation.svg' height='60%' width='60%'/>\n",
    "    <figcaption><b>Figure 2: Amplitude modulation of a baseband signal.</b></figcaption>\n",
    "</figure>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "First off, we establish the basic paremeters for our design. Gen 1 RF ADCs have a sampling frequency of 4096MHz, so we will simulate that in this exercise. We choose $f_{s}$ = 4096MHz and a baseband input signal of $f_{b}$ = 64MHz."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Set basic params\n",
    "fs = 4096e6 # sample rate\n",
    "fb = 64e6 # frequency of baseband signal\n",
    "A = 2 # baseband signal amplitude\n",
    "N_fft = 2048 # fft size\n",
    "\n",
    "t = np.arange(N_fft)/fs #time scale"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Next, we define an input signal, $g(t)$, as\n",
    "\n",
    "$$\n",
    "g(t) = A\\cos(2 \\pi f_{b} t),\n",
    "$$\n",
    "\n",
    "where A is the amplitude and $f_{b}$ is the frequency of the baseband signal.\n",
    "\n",
    "For demonstration purposes, a single tone signal will be used in this example. However, a typical baseband signal with frequency components from 0 to $f_{b}$ can be modulated with the same approach."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Define input signal\n",
    "g = A*np.cos(2*np.pi*fb*t) "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The FFT of the signal is now found for frequency domain plotting. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Calculate FFT\n",
    "g_fft_result = np.fft.fft(g, N_fft)\n",
    "\n",
    "# Get the corresponding frequencies, that depend on N_fft and Fs - freq. domain x axis\n",
    "freqs = np.fft.fftfreq(N_fft,1/fs)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "This baseband information signal can be plotted in both time and frequency domains. \n",
    "\n",
    "We can observe a cosine with amplitude $A$ in the time domain and a single tone at $f_{b}$ in the frequency domain."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "fig, axs = plt.subplots(1,2, figsize=(15,4))\n",
    "axs[0].plot(g[:200])\n",
    "axs[0].set_title('Time Domain Baseband Signal')\n",
    "axs[0].set_xlabel('Samples')\n",
    "axs[0].set_ylabel('Amplitude')\n",
    "\n",
    "axs[1].plot(freqs[:int(N_fft/2)]/1e6, np.abs(g_fft_result[:int(N_fft/2)]))\n",
    "axs[1].set_title('One Sided FFT plot of the Baseband Signal')\n",
    "axs[1].set_ylabel('Magnitude')\n",
    "axs[1].set_xlabel('Frequency, MHz')\n",
    "fig.tight_layout()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Similarly, a high frequency carrier signal can be defined. This signal has a much higher frequency that is used to modulate the information signal to the desired IF or RF band.\n",
    "\n",
    "$$\n",
    "c(t) = \\cos(2 \\pi f_{c} t)\n",
    "$$"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "fc = 1400e6 # Carrier Frequency \n",
    "\n",
    "# Define Carrier Signal\n",
    "c = np.cos(2*np.pi*fc*t) "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Find FFT of Carrier\n",
    "c_fft_result = np.fft.fft(c, N_fft)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We can now plot this carrier signal in the time and frequency domains. This signal has a higher frequency than the baseband signal and comprises of a single tone at $f_{c}$ in the frequency domain."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "fig, axs = plt.subplots(1,2, figsize=(15,4))\n",
    "axs[0].plot(c[:100])\n",
    "axs[0].set_title('Time Domain Carrier Signal')\n",
    "axs[0].set_xlabel('Samples')\n",
    "axs[0].set_ylabel('Amplitude')\n",
    "\n",
    "axs[1].plot(freqs[:int(N_fft/2)]/1e6, np.abs(c_fft_result[:int(N_fft/2)]))\n",
    "axs[1].set_title('One Sided FFT plot of the Carrier Signal')\n",
    "axs[1].set_ylabel('Magnitude')\n",
    "axs[1].set_xlabel('Frequency, MHz')\n",
    "fig.tight_layout()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "A modulated signal, $s(t)$, can now be obtained by multiplying our information signal, $g(t)$, by the carrier signal $c(t)$.\n",
    "\n",
    "$$\n",
    "s(t) = c(t) \\times g(t)\n",
    "$$\n",
    "\n",
    "$$\n",
    "s(t) = \\cos(2 \\pi f_{c} t) \\times A\\cos(2 \\pi f_{b} t)\n",
    "$$\n",
    "\n",
    "Using trigonometry we can see that\n",
    "\n",
    "$$\n",
    "s(t) = \\frac{A}{2} \\cos(2 \\pi (f_{c} - f_{b}) t) + \\frac{A}{2} \\cos(2 \\pi (f_{c} + f_{b}) t).\n",
    "$$\n",
    "\n",
    "In Python, this modulated signal can be easily obtained by mutliplying the carrier signal $c(t)$ by the information signal, $g(t)$, as below."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Find Modulated Signal\n",
    "s = g*c # Information signal, g(t), multiplied by carrier signal, c(t)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Find FFT of Modulated Signal\n",
    "s_fft_result = np.fft.fft(s, N_fft)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We can plot the results of this modulation in the time and frequency domains. \n",
    "\n",
    "Frequency components can be seen at $f_{c} - f_{b}$ and $f_{c} + f_{b}$. Therefore, the bandwidth occupied by the transmitted signal is $2 f_{b}$"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "fig, axs = plt.subplots(1,2, figsize=(15,4))\n",
    "axs[0].plot(s[:200])\n",
    "axs[0].set_title('Time Domain Modulated Signal')\n",
    "axs[0].set_xlabel('Samples')\n",
    "axs[0].set_ylabel('Amplitude')\n",
    "\n",
    "axs[1].plot(freqs[:int(N_fft/2)]/1e6, np.abs(s_fft_result[:int(N_fft/2)]))\n",
    "axs[1].set_title('One Sided FFT plot of the Modulated Signal')\n",
    "axs[1].set_ylabel('Magnitude')\n",
    "axs[1].set_xlabel('Frequency, MHz')\n",
    "fig.tight_layout()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 4. Demodulation of the Baseband Signal <a class=\"anchor\" id=\"demod\"></a>\n",
    "\n",
    "At the receiver, this modulated signal can be 'perfectly' demodulated using a local oscillator that matches the transmitter's carrier. The RF receiver design can be seen in Figure 3."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<figure>\n",
    "<img src='./images/amplitude_demodulation.svg' height='60%' width='60%'/>\n",
    "    <figcaption><b>Figure 3: Amplitude Demodulation of a Baseband Signal</b></figcaption>\n",
    "</figure>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Demodulation can be seen as the reverse process of modulation. The modulated signal, $s(t)$, is multiplied by the carrier signal, $c(t)$, to produce a demodulated signal, $x(t)$. In the ideal scenario, the receiver carrier signal would be equivalent to $c_{t}$ in the transmitter.\n",
    "\n",
    "$$\n",
    "x(t) = c(t) \\times s(t)\n",
    "$$\n",
    "\n",
    "This relationship correctly reconstructs the input signal. However, unwanted high frequency components remain at multiples of $f_{c}$.\n",
    "\n",
    "$$x(t) = \\frac{A}{2} \\cos(2 \\pi f_{b} t) + \\frac{A}{4} \\cos(2 \\pi (2f_{c} - f_{b}) t) + \\frac{A}{4} \\cos(2 \\pi (2f_{c} + f_{b}) t)$$\n",
    "\n",
    "Within Python, this demodulation can be performed by multiplying the carrier signal, $c(t)$, by the received signal, $s(t)$."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Find Demodulated Signal\n",
    "x = c*s # multiplication of carrier and received signals to perform demodulation"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Find FFT of Demodulated Signal\n",
    "x_fft_result = np.fft.fft(x, N_fft)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "This demodulated signal can now be plotted in the time and frequency domains. \n",
    "\n",
    "We can see that the original information signal at $f_{b}$ has been successfully recovered. However, unwanted high frequency components sitting at $2f_{c} - f_{b}$ and $2f_{c} + f_{b}$ are present."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "fig, axs = plt.subplots(1,2, figsize=(15,4))\n",
    "axs[0].plot(x[:200])\n",
    "axs[0].set_title('Time Domain Unfiltered Demodulated Signal')\n",
    "axs[0].set_xlabel('Samples')\n",
    "axs[0].set_ylabel('Amplitude')\n",
    "\n",
    "axs[1].plot(freqs[:int(N_fft/2)]/1e6, np.abs(x_fft_result[:int(N_fft/2)]))\n",
    "axs[1].set_title('One Sided FFT plot of the Unfiltered Demodulated Signal')\n",
    "axs[1].set_ylabel('Magnitude')\n",
    "axs[1].set_xlabel('Frequency, MHz')\n",
    "fig.tight_layout()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "As we can see from the above figure, the information signal has been successfully demodulated. However, unwanted high frequency components remain in the signal. These components can be removed using a lowpass filter, which we will design using the window method. This approach simply windows an ideal impulse response $h_d(n)$ by multiplying it by a window function $w(n)$, as follows.\n",
    "\n",
    "$$\n",
    "h(n) = w(n).h_d(n)\n",
    "$$"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "f_cutoff = 0.1 # Cutoff frequency as a fraction of the sampling rate\n",
    "b = 0.08  # Transition band, as a fraction of the sampling rate (in (0, 0.5)).\n",
    "\n",
    "N = int(np.ceil((4 / b)))\n",
    "if not N % 2: N += 1  # N is odd.\n",
    "n = np.arange(N)\n",
    "\n",
    "h = np.sinc(2 * f_cutoff * (n - (N - 1) / 2)) # Compute sinc filter.\n",
    "w = np.blackman(N) # Compute Blackman window.\n",
    "h = h * w # Multiply sinc filter by window.\n",
    "h = h / np.sum(h) # Normalize to get unity gain."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The lowpass filter can be applied to the demodulated signal using convolution. Within Python, this operation can be performed simply by using `np.convolve()`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "u = np.convolve(x, h)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The results of this low pass filter can be plotted, alongside the unfiltered signal. \n",
    "\n",
    "The 'cost' of amplitude scaling demodulation, as the name suggests, is a 50% amplitude scaling. The scaling is particularly clear in the time domain plot. In the frequency domain, we can clearly see that the high frequency components are removed, leaving only the desired baseband signal.\n",
    "\n",
    "With the removal of the high frequency components, the desired output can be obtained as,\n",
    "\n",
    "$$\n",
    "u(t) = \\frac{A}{2} \\cos(2 \\pi f_{b} t).\n",
    "$$"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "u_fft_result = np.fft.fft(u, N_fft)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "fig, axs = plt.subplots(1,2, figsize=(15,4))\n",
    "axs[0].plot(u[:200])\n",
    "axs[0].set_title('Time Domain Lowpass Filtered Demodulated Signal')\n",
    "axs[0].set_xlabel('Samples')\n",
    "axs[0].set_ylabel('Amplitude')\n",
    "\n",
    "axs[1].plot(freqs[:int(N_fft/2)]/1e6, np.abs(u_fft_result[:int(N_fft/2)]))\n",
    "axs[1].set_title('One Sided FFT plot of the Lowpass Filtered Demodulated Signal')\n",
    "axs[1].set_ylabel('Magnitude')\n",
    "axs[1].set_xlabel('Frequency, MHz')\n",
    "fig.tight_layout()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 5. Demodulation Phase Error <a class=\"anchor\" id=\"phase_error\"></a>\n",
    "\n",
    "If the local oscillator is not exactly in phase with the received signal, the resulting phase error will cause an attenuation by a factor of $\\cos(\\theta)$, as shown below.\n",
    "\n",
    "$$\n",
    "\\cos(2 \\pi f_{b} t + \\theta)\n",
    "$$"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Find phaseshifted local carrier\n",
    "phaseshift = (np.pi/3) #60 degree phase shift\n",
    "phase_carrier = np.cos(2*np.pi*fc*t + phaseshift) # Apply phaseshift to local receiver Carrier\n",
    "\n",
    "# Find demodulated signal using new carrier - Modulated signal remains identical to before\n",
    "phase_demodulation = phase_carrier*s # Demodulate Signal\n",
    "\n",
    "# Apply Lowpass filter to demodulated signal\n",
    "lp_phaseshift = np.convolve(phase_demodulation, h)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "This concept can be shown by plotting the low pass filtered demodulated signal again, but incorporating a phase error into the local carrier signal.\n",
    "\n",
    "By using a phase error of $\\frac{\\pi}{3}$ we observe an attenuation of $\\cos(\\frac{\\pi}{3}) = 0.5.$\n",
    "\n",
    "Therefore, in the time domain, we can observe an amplitude of $\\frac{A}{4}$ compared to the transmitted signal. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Find PhaseShifted FFT Signal\n",
    "phaseshift_fft_result = np.fft.fft(lp_phaseshift, N_fft)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "fig, axs = plt.subplots(1,2, figsize=(15,4))\n",
    "axs[0].plot(u[:200])\n",
    "axs[0].plot(lp_phaseshift[:200])\n",
    "axs[0].set_title('Time Domain Lowpass Filtered Demodulated Signal')\n",
    "axs[0].set_xlabel('Samples')\n",
    "axs[0].set_ylabel('Amplitude')\n",
    "axs[0].legend(('In phase Carrier', 'Phaseshifted Carrier'))\n",
    "\n",
    "axs[1].plot(freqs[:int(N_fft/2)]/1e6, np.abs(u_fft_result[:int(N_fft/2)]))\n",
    "axs[1].plot(freqs[:int(N_fft/2)]/1e6, np.abs(phaseshift_fft_result[:int(N_fft/2)]))\n",
    "axs[1].set_title('One Sided FFT plot of the Lowpass Filtered Demodulated Signal')\n",
    "axs[1].set_ylabel('Magnitude')\n",
    "axs[1].set_xlabel('Frequency, MHz')\n",
    "axs[1].legend(('In phase Carrier', 'Phaseshifted Carrier'))\n",
    "fig.tight_layout()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 6. Conclusion <a class=\"anchor\" id=\"conclusion\"></a>\n",
    "\n",
    "So far we have shown how amplitude modulation may be used to transmit a baseband signal at much higher frequencies whilst being able to recreate the original signal at the receiver. However there is an issue of spectral inefficiency - the baseband is $f_{b}$ Hz wide but the modulated signal is $2f_{b}$. Therefore, we require double the bandwidth to transmit the signal.\n",
    "\n",
    "This problem can be addressed using QAM modulation schemes, addressed in the next notebook."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "---\n",
    "\n",
    "[⬅️ Previous Notebook](../notebook_D/03_pulse_shaping.ipynb) || [Next Notebook 🚀](02_qam_modulation.ipynb)\n",
    "\n",
    "Copyright © 2023 Strathclyde Academic Media\n",
    "\n",
    "---\n",
    "---"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.8.2"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
